package se480.tracker.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.log4j.Logger;

import se480.tracker.event.TrackerEvent;
import se480.tracker.processor.Processor;
import se480.tracker.tracker.Location;
import se480.tracker.tracker.SynchableTracker;
import se480.tracker.tracker.Tracker;
import se480.tracker.tracker.TrackerA;
import se480.tracker.tracker.TrackerB;
import se480.tracker.tracker.TrackerObserver;
import se480.tracker.util.OutputFileWriterUtil;

/**
 * This class manages client registration for synchronous and asynchronous
 * services.
 * It also manages processor health and fail over.
 * 
 * @author mmichalak
 *
 */
public class RegistrationService {
	static Logger logger = Logger.getLogger(RegistrationService.class.getName());
	
	Processor primaryProc = null;
	Processor backupProc = null;
	
	// Observers to get new Tracker upon failover
	ArrayList<TrackerObserver> aTrackerObs = new ArrayList<TrackerObserver>();
	ArrayList<TrackerObserver> bTrackerObs = new ArrayList<TrackerObserver>();
	
	
	/**
	 * This performs a failover.
	 * 
	 * @param ev
	 */
	public void failOver(TrackerEvent ev) {

		StringBuilder sb = new StringBuilder();
		sb.append("FAILURE OF PROCESSOR ").append(primaryProc.getId()).append(" at timestamp ").append(ev.getTick());
		logger.debug(sb.toString());
		OutputFileWriterUtil.writeToFile(sb.toString());
		
		Processor failedProc = primaryProc;
		primaryProc = backupProc;
		
		Tracker p1TrackA = new TrackerA();
		failedProc.setTrackerA(p1TrackA);
		Tracker p1TrackB = new TrackerB();
		failedProc.setTrackerB(p1TrackB);
		backupProc = failedProc;
		
		 // Push outstanding partials into backup's datastore
		SynchableTracker a = (SynchableTracker)primaryProc.getTrackerA();
		a.synchPartialCheckpoint(backupProc.getTrackerA());
		
		// NOTIFY Tracker Observers of the failure and provide new Tracker obj
		for (TrackerObserver obsa : aTrackerObs) {
			obsa.updateTracker(primaryProc.getTrackerA());
		}
		for (TrackerObserver obsb : bTrackerObs) {
			obsb.updateTracker(primaryProc.getTrackerB());
		}
	}

	
	/**
	 * This is the less-freq full dump of datastore
	 * to another datastore.  It copies the checkpoint
	 * directly to the backup processor datastore
	 */
	public void executeFullCheckpoint() {
		Map<String, ArrayList<Location>> dataStore = primaryProc.getDataStore();
		Map<String, ArrayList<Location>> cloneDs = new HashMap<String, ArrayList<Location>>();
		
		Iterator<String> iter = dataStore.keySet().iterator();
		while (iter.hasNext()) {
			String id = iter.next();
			ArrayList<Location> locs = dataStore.get(id);
			@SuppressWarnings("unchecked")
			ArrayList<Location> cloneLocs = (ArrayList<Location>)locs.clone(); // clone list and Loc objs
			cloneDs.put(id, cloneLocs);
		}
		
		if (backupProc != null)
			 backupProc.setDataStore(cloneDs);
		
		logger.debug("Finished Full Checkpoint...");
	}
	
	/**
	 * This is the more-freq partial update consisting of 
	 * log latest events since last full checkpoint
	 */
	public void executePartialCheckpoint() {
		logger.debug("Beginning Partial Checkpoint...");
		SynchableTracker a = (SynchableTracker)primaryProc.getTrackerA();
		/*
		 * This takes all the queued Events and does the add
		 * to the backup's datastore
		 */
		a.synchPartialCheckpoint(backupProc.getTrackerA());
	}


	/**
	 * This method also registers the requestor as an observer
	 * of the TrackerA object so it will get notified upon failover
	 * 
	 * @param obs
	 * @return
	 */
	public Tracker getTrackerA(TrackerObserver obs) {
		aTrackerObs.add(obs);
		return primaryProc.getTrackerA();
	}
	
	/**
	 * This method also registers the requestor as an observer
	 * of the TrackerB object so it will get notified upon failover
	 * 
	 * @param obs
	 * @return
	 */
	public Tracker getTrackerB(TrackerObserver obs) {
		bTrackerObs.add(obs);
		return primaryProc.getTrackerB();
	}
	

	public Processor getPrimaryProc() {
		return primaryProc;
	}

	public void setPrimaryProc(Processor primaryProc) {
		this.primaryProc = primaryProc;
	}

	public Processor getBackupProc() {
		return backupProc;
	}

	public void setBackupProc(Processor backupProc) {
		this.backupProc = backupProc;
	}
}
